package com.gpj.learn.rabbitmq.springboot.system;

import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @className RabbitMQConfigRegistrar
 * @description RabbitMQ相关对象定义
 * @author GPJ
 * @date 2021/9/3 16:15
 * @version 1.0
 **/
public class RabbitMQConfigRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {
	private static final String PROPERTY_CONFIG_RABBITMQ_KEY = "mediax.common.rabbitmq";
	private static final String LIST_PROPERTY_CONFIG_TEMPLATE = "%s[%s].%s";
	private Environment environment;

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry, BeanNameGenerator beanNameGenerator) {
		List<RabbitMQProperty> properties = getRabbitMQProperties();
		if (properties.isEmpty()) return;

		for (RabbitMQProperty property : properties) {
			rabbitmqInfoDefinitions(property, registry);
		}
	}

	/**
	 * 注册RabbitMQ所需队列,交换机和绑定对象的BeanDefinition
	 * @param property RabbitMQ配置
	 * @param registry spring注册对象
	 */
	private void rabbitmqInfoDefinitions(RabbitMQProperty property, BeanDefinitionRegistry registry) {
		// 注册【队列】对象的BeanDefinition
		boolean existQueue = StringUtils.isNotBlank(property.getQueue());
		if (existQueue && !registry.containsBeanDefinition(property.getQueue())) {
			BeanDefinitionBuilder queueBuilder = BeanDefinitionBuilder.genericBeanDefinition(Queue.class);
			queueBuilder.addConstructorArgValue(property.getQueue());
			queueBuilder.addConstructorArgValue(true);
			registry.registerBeanDefinition(property.getQueue(), queueBuilder.getBeanDefinition());
		}

		// 注册【交换机】对象的BeanDefinition
		boolean existExchange = StringUtils.isNotBlank(property.getExchange());
		if (existExchange && !registry.containsBeanDefinition(property.getExchange())) {
			BeanDefinitionBuilder exchangeBuilder = BeanDefinitionBuilder.genericBeanDefinition(property.generateExchangeClass());
			exchangeBuilder.addConstructorArgValue(property.getExchange());
			registry.registerBeanDefinition(property.getExchange(), exchangeBuilder.getBeanDefinition());
		}

		// 注册【绑定】对象的BeanDefinition
		if (existQueue && existExchange) {
			BeanDefinitionBuilder exchangeBuilder = BeanDefinitionBuilder.genericBeanDefinition(BindingFactoryBean.class);
			exchangeBuilder.addConstructorArgReference(property.getQueue());
			exchangeBuilder.addConstructorArgReference(property.getExchange());
			exchangeBuilder.addConstructorArgValue(property.getRoutingKey());
			registry.registerBeanDefinition(property.getQueue() + "_" + property.getExchange() + UUID.randomUUID().toString(), exchangeBuilder.getBeanDefinition());
		}
	}

	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
	}

	/**
	 * 获取RabbitMQ配置信息
	 */
	private List<RabbitMQProperty> getRabbitMQProperties() {
		try {

			return getProperties(PROPERTY_CONFIG_RABBITMQ_KEY, RabbitMQProperty.class);
		} catch (IllegalAccessException | InstantiationException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 获取配置信息,并转换为指定类对象
	 * @param key 配置信息的key
	 * @param clazz 要转化的目标对象的类
	 */
	private <T> List<T> getProperties(String key, Class<T> clazz) throws IllegalAccessException, InstantiationException {
		List<T> list = new ArrayList<>();

		Field[] fields = clazz.getDeclaredFields();
		for (int i = 0; ; i++) {
			T instance = clazz.newInstance();

			boolean notNull = false;
			for (Field field : fields) {
				String realKey = String.format(LIST_PROPERTY_CONFIG_TEMPLATE, key, i, field.getName());
				Object value = environment.getProperty(realKey, field.getType());
				if (value != null) {
					field.setAccessible(true);
					field.set(instance, value);
					notNull = true;
				}
			}

			if (notNull) {
				list.add(instance);
				notNull = false;
			} else {
				break;
			}
		}
		return list;
	}
}
